Learn in 10 minutes

Learn in 10 minutes

10분 안에 Go 배우기

Go(또는 Golang)는 Google에서 설계한 정적 타입의 컴파일 프로그래밍 언어입니다. 단순함, 효율성, 그리고 뛰어난 동시성 지원으로 유명합니다. 이 튜토리얼은 Go 프로그래밍을 빠르게 배울 수 있도록 도와줍니다.

1. 첫 번째 Go 프로그램 작성하기

간단한 프로그램부터 시작해보겠습니다. hello.go 파일을 생성하고 다음 코드를 입력하세요:

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

파일을 저장하고 터미널에서 다음 명령어를 실행하세요:

go run hello.go

출력 결과:

Hello, World!

이 간단한 프로그램은 Go의 기본 구조를 보여줍니다:

  • package main은 패키지 이름을 선언합니다
  • import "fmt"는 I/O 작업을 위한 포맷 패키지를 가져옵니다
  • func main()은 프로그램의 진입점입니다
  • fmt.Println()은 텍스트를 콘솔에 출력합니다

2. 기본 문법

Go는 깔끔하고 간단한 문법을 가지고 있습니다. Python과 달리 Go는 중괄호 {}를 사용하여 코드 블록을 정의하고 문장 끝에 세미콜론이 필요합니다(보통 자동으로 삽입됩니다).

// 이것은 한 줄 주석입니다
fmt.Println("Hello, World!")

/*
이것은 여러 줄에 걸친
여러 줄 주석입니다
*/

Go의 기본 문법 규칙:

  • 코드 블록: 중괄호 {}로 정의됩니다
  • 주석: 한 줄 주석은 //로 시작하고, 여러 줄 주석은 /* */로 시작합니다
  • 문장: 세미콜론으로 끝납니다(자동 삽입)
  • 패키지 선언: 모든 파일은 패키지 선언으로 시작합니다

3. 변수와 데이터 타입

Go는 정적 타입 언어로, 변수 타입을 선언해야 합니다. 그러나 Go는 := 연산자를 사용한 타입 추론을 지원합니다.

변수 선언 방법:

// 명시적 타입 선언
var name string = "John"
var age int = 25

// 타입 추론
name := "John"
age := 25

// 여러 변수 선언
var x, y int = 10, 20
x, y := 10, 20

Go의 주요 기본 데이터 타입:

  • 정수 타입: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr
  • 실수 타입: float32, float64
  • 문자열: string
  • 불리언: bool
  • 복소수 타입: complex64, complex128

3.1 숫자 타입

Go는 다양한 사용 사례에 맞는 여러 숫자 타입을 제공합니다:

// 정수 타입
var age int = 25
var smallNumber int8 = 127
var largeNumber int64 = 9223372036854775807

// 실수 타입
var temperature float32 = 36.5
var pi float64 = 3.14159265359

// 복소수
var complexNum complex64 = 3 + 4i

3.2 문자열 타입

Go의 문자열은 바이트 시퀀스이며 불변입니다:

// 문자열 선언
var greeting string = "Hello, Go!"
name := "Alice"

// 문자열 연산
fmt.Println(len(greeting))        // 문자열 길이
fmt.Println(greeting[0])          // 첫 번째 문자 접근(바이트)
fmt.Println(greeting[0:5])        // 문자열 슬라이싱
fmt.Println(strings.ToUpper(name)) // 대문자로 변환

3.3 불리언 타입

불리언 타입은 truefalse 두 가지 값을 가집니다:

var isActive bool = true
var isComplete bool = false

// 불리언 연산
result1 := true && false  // false
result2 := true || false  // true
result3 := !true          // false

4. 상수

상수는 const 키워드를 사용하여 선언되며 변경할 수 없습니다:

const Pi = 3.14159
const MaxUsers = 1000

// 여러 상수
const (
    StatusOK = 200
    StatusNotFound = 404
    StatusError = 500
)

// 타입이 지정된 상수
const Version string = "1.0.0"

5. 데이터 구조

Go는 데이터 저장과 조작을 위한 여러 내장 데이터 구조를 제공합니다.

5.1 배열

배열은 같은 타입의 요소로 이루어진 고정 크기 시퀀스입니다:

// 배열 선언
var numbers [5]int = [5]int{1, 2, 3, 4, 5}
names := [3]string{"Alice", "Bob", "Charlie"}

// 요소 접근
fmt.Println(numbers[0])  // 1
numbers[0] = 10         // 요소 수정

// 배열 길이
fmt.Println(len(numbers)) // 5

5.2 슬라이스

슬라이스는 크기가 동적으로 증가하고 감소할 수 있는 배열입니다:

// 슬라이스 선언
numbers := []int{1, 2, 3, 4, 5}
var emptySlice []int

// 배열에서 슬라이스 생성
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4]  // [2, 3, 4]

// 슬라이스 연산
numbers = append(numbers, 6)      // 요소 추가
numbers = append(numbers, 7, 8, 9) // 여러 요소 추가

// 슬라이스 용량과 길이
fmt.Println(len(numbers)) // 길이: 9
fmt.Println(cap(numbers)) // 용량: 10 (변동 가능)

5.3 맵

맵은 키-값 쌍의 정렬되지 않은 컬렉션입니다:

// 맵 선언
student := map[string]interface{}{
    "name": "John",
    "age":  20,
    "major": "Computer Science",
}

// 대체 선언
var scores map[string]int = make(map[string]int)
scores["math"] = 95
scores["science"] = 88

// 접근 및 수정
fmt.Println(student["name"])
student["age"] = 21
student["gpa"] = 3.8

// 안전한 접근
if phone, exists := student["phone"]; exists {
    fmt.Println(phone)
} else {
    fmt.Println("전화번호가 제공되지 않았습니다")
}

// 맵 순회
for key, value := range student {
    fmt.Printf("%s: %v\n", key, value)
}

5.4 구조체

구조체는 서로 다른 타입을 가질 수 있는 필드들의 모음입니다:

// 구조체 정의
type Person struct {
    Name string
    Age  int
    City string
}

// 구조체 인스턴스 생성
person1 := Person{"Alice", 25, "New York"}
person2 := Person{
    Name: "Bob",
    Age:  30,
    City: "London",
}

// 필드 접근
fmt.Println(person1.Name)
person1.Age = 26

6. 연산과 연산자

Go는 다양한 계산과 비교를 위한 풍부한 연산자 집합을 제공합니다.

  • 산술 연산자: +, -, *, /, % (나머지)
  • 비교 연산자: ==, !=, >, <, >=, <=
  • 논리 연산자: &&, ||, !
  • 비트 연산자: &, |, ^, <<, >>
  • 할당 연산자: =, +=, -=, *=, /=

6.1 산술 연산자

a, b := 10, 3

fmt.Printf("덧셈: %d\n", a + b)      // 13
fmt.Printf("뺄셈: %d\n", a - b)   // 7
fmt.Printf("곱셈: %d\n", a * b)  // 30
fmt.Printf("나눗셈: %d\n", a / b)      // 3
fmt.Printf("나머지: %d\n", a % b)      // 1

6.2 비교 연산자

x, y := 5, 10

fmt.Printf("같음: %t\n", x == y)     // false
fmt.Printf("다름: %t\n", x != y) // true
fmt.Printf("크다: %t\n", x > y)  // false
fmt.Printf("작다: %t\n", x < y)  // true

6.3 논리 연산자

a, b := true, false

fmt.Printf("AND 연산: %t\n", a && b)  // false
fmt.Printf("OR 연산: %t\n", a || b)    // true
fmt.Printf("NOT 연산: %t\n", !a)    // false

7. 제어 흐름

Go는 프로그램 실행을 관리하기 위한 여러 제어 흐름 문을 제공합니다.

7.1 if 문

age := 20
if age >= 18 {
    fmt.Println("성인")
} else if age >= 13 {
    fmt.Println("청소년")
} else {
    fmt.Println("어린이")
}

// 짧은 문장과 함께하는 if
if score := 85; score >= 90 {
    fmt.Println("성적: A")
} else if score >= 80 {
    fmt.Println("성적: B")
} else {
    fmt.Println("성적: C")
}

7.2 for 반복문

Go에는 단 하나의 반복 구조인 for만 있습니다:

// 기본 for 반복문
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

// While 스타일 반복문
count := 0
for count < 5 {
    fmt.Println(count)
    count++
}

// 무한 반복문
for {
    fmt.Println("이것은 영원히 실행됩니다")
    break // 탈출에 break 사용
}

// Range 반복문 (슬라이스, 배열, 맵용)
fruits := []string{"apple", "banana", "cherry"}
for index, fruit := range fruits {
    fmt.Printf("%d: %s\n", index, fruit)
}

7.3 switch 문

day := "Monday"
switch day {
case "Monday":
    fmt.Println("주 시작")
case "Friday":
    fmt.Println("주말이 가까워요")
case "Saturday", "Sunday":
    fmt.Println("주말!")
default:
    fmt.Println("평일")
}

// 표현식 없는 switch
score := 85
switch {
case score >= 90:
    fmt.Println("성적: A")
case score >= 80:
    fmt.Println("성적: B")
case score >= 70:
    fmt.Println("성적: C")
default:
    fmt.Println("성적: F")
}

8. 함수

Go의 함수는 일급 시민이며 여러 반환 값을 지원합니다.

8.1 기본 함수

func greet(name string) string {
    return "Hello, " + name + "!"
}

// 함수 호출
message := greet("John")
fmt.Println(message)

8.2 여러 반환 값

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("0으로 나눌 수 없습니다")
    }
    return a / b, nil
}

// 여러 반환 값 사용
result, err := divide(10, 2)
if err != nil {
    fmt.Println("오류:", err)
} else {
    fmt.Println("결과:", result)
}

8.3 명명된 반환 값

func calculateRectangle(width, height float64) (area float64, perimeter float64) {
    area = width * height
    perimeter = 2 * (width + height)
    return // naked return
}

area, perimeter := calculateRectangle(5, 3)
fmt.Printf("면적: %.2f, 둘레: %.2f\n", area, perimeter)

8.4 가변 인자 함수

func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

fmt.Println(sum(1, 2, 3, 4))  // 10
fmt.Println(sum(5, 10, 15))   // 30

9. 포인터

Go는 C/C++보다 더 간단한 문법으로 포인터를 가지고 있습니다:

func modifyValue(x *int) {
    *x = *x * 2
}

func main() {
    value := 10
    fmt.Println("이전:", value) // 10

    modifyValue(&value)
    fmt.Println("이후:", value)  // 20
}

10. 메서드

메서드는 리시버 인자를 가진 함수입니다:

type Rectangle struct {
    Width  float64
    Height float64
}

// 값 리시버를 가진 메서드
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// 포인터 리시버를 가진 메서드
func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

rect := Rectangle{Width: 5, Height: 3}
fmt.Println("면적:", rect.Area()) // 15

rect.Scale(2)
fmt.Println("확장된 면적:", rect.Area()) // 60

11. 인터페이스

인터페이스는 타입이 구현할 수 있는 메서드 시그니처를 정의합니다:

type Shape interface {
    Area() float64
    Perimeter() float64
}

type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return 3.14159 * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * 3.14159 * c.Radius
}

func printShapeInfo(s Shape) {
    fmt.Printf("면적: %.2f, 둘레: %.2f\n", s.Area(), s.Perimeter())
}

circle := Circle{Radius: 5}
printShapeInfo(circle)

12. 오류 처리

Go는 예외 대신 명시적 오류 처리를 사용합니다:

func readFile(filename string) (string, error) {
    data, err := os.ReadFile(filename)
    if err != nil {
        return "", fmt.Errorf("파일 %s 읽기 실패: %w", filename, err)
    }
    return string(data), nil
}

content, err := readFile("example.txt")
if err != nil {
    fmt.Println("오류:", err)
    return
}
fmt.Println("내용:", content)

13. 고루틴을 이용한 동시성

고루틴은 Go 런타임이 관리하는 경량 스레드입니다:

func worker(id int) {
    for i := 0; i < 3; i++ {
        fmt.Printf("작업자 %d: %d\n", id, i)
        time.Sleep(time.Millisecond * 100)
    }
}

func main() {
    // 여러 고루틴 시작
    for i := 1; i <= 3; i++ {
        go worker(i)
    }

    // 고루틴 완료 대기
    time.Sleep(time.Second)
    fmt.Println("모든 작업자 완료")
}

14. 채널

채널은 고루틴 간 통신에 사용됩니다:

func producer(ch chan<- int) {
    for i := 0; i < 5; i++ {
        ch <- i // 채널에 값 전송
        time.Sleep(time.Millisecond * 100)
    }
    close(ch) // 완료 시 채널 닫기
}

func consumer(ch <-chan int) {
    for value := range ch {
        fmt.Println("수신:", value)
    }
}

func main() {
    ch := make(chan int, 3) // 버퍼 채널

    go producer(ch)
    consumer(ch)

    fmt.Println("채널 통신 완료")
}

15. 파일 작업

Go는 파일 읽기와 쓰기를 위한 간단한 메서드를 제공합니다:

// 파일 읽기
data, err := os.ReadFile("example.txt")
if err != nil {
    fmt.Println("파일 읽기 오류:", err)
    return
}
fmt.Println("파일 내용:", string(data))

// 파일 쓰기
content := "Hello, Go!\n"
err = os.WriteFile("output.txt", []byte(content), 0644)
if err != nil {
    fmt.Println("파일 쓰기 오류:", err)
    return
}
fmt.Println("파일 쓰기 성공")

16. 패키지와 모듈

Go 모듈은 의존성과 패키지 버전을 관리합니다:

// 표준 라이브러리 패키지 가져오기
import (
    "fmt"
    "math"
    "strings"
)

func main() {
    fmt.Println(math.Sqrt(16))        // 4
    fmt.Println(strings.ToUpper("go")) // GO
}

자신만의 패키지를 생성하려면 패키지 이름으로 디렉토리를 만들고 함수 이름을 대문자로 시작하여 내보내세요.

17. 테스트

Go는 내장 테스트 지원을 가지고 있습니다:

// math_test.go 파일에서
package main

import "testing"

func TestAdd(t *testing.T) {
    result := add(2, 3)
    expected := 5
    if result != expected {
        t.Errorf("add(2, 3) = %d; want %d", result, expected)
    }
}

func add(a, b int) int {
    return a + b
}

테스트 실행: go test

18. 모범 사례

  • gofmt를 사용하여 코드를 포맷팅하세요
  • Go 명명 규칙을 따르세요(변수는 camelCase, 내보내기는 PascalCase)
  • 오류를 명시적으로 처리하세요
  • 추상화를 위해 인터페이스를 사용하세요
  • 상속보다 구성을 선호하세요
  • 포괄적인 테스트를 작성하세요
  • 가능하면 표준 라이브러리를 사용하세요

이 튜토리얼은 Go 프로그래밍의 필수 기능을 다루고 있습니다. 연습을 통해 Go의 강력한 기능을 사용하여 효율적이고 동시적인 애플리케이션을 구축할 수 있을 것입니다.